/**
 * 参与者模式
 */


/***
 * https://juejin.cn/post/6986883556660232205
 * 函数反柯里化
 * 
 * 题目如下：var a = Function.prototype.call.apply(function(a){return a;}, [0,4,3]);alert(a);

分析步骤如下：
1、将Function.prototype.call当成整体，call方法是由浏览器实现的本地方法，是函数类型的内部方法
var a = (Function.prototype.call).apply(function(a){return a;}, [0,4,3]); // 自右向左分析

2、fun.apply(obj,args)等价于obj.fun(args)，这一步是重点，必须理解！
(function(a){return a;}).apply(0,[4,3])
(function(a){return a;}).call(0,4,3)  //可以看下面的80行附近的举例


3、 到这步结果就很明显了，a就是4，alert的结果就是4
 * 
 */

/**
 * 柯里化（Currying），又称部分求值（Partial Evaluation），可以理解为提前接收部分参数，延迟执行，不立即输出结果，而是返回一个接受剩余参数的函数。
 * 因为这样的特性，也被称为部分计算函数。柯里化，是一个逐步接收参数的过程。
 */
// 柯里化
Function.prototype.carrying = function(fn){
    const Slice = [].slice
    let args = Slice.call(arguments,1) //第一次传进来的参数

    let innerFunc = function(){ 
        var funArgs = Slice.call(arguments) // 第n次(n>2)/执行时 传进来的参数
        
        if(!arguments.length){
            return fn.apply(null,args) // 不传入参数则执行
        }else{
            // 否则收集参数， 返回当前函数（递归）
            args.push(...funArgs)
            return innerFunc
        }
    }

    return innerFunc
}

function add(){
    const nums = [].slice.call(arguments)
    return nums.reduce((pre,sum)=>pre+sum, 0)
}

const add7and8 = add.carrying(add,7,8)
add7and8(9)(10)(11)
console.log( add7and8() )

/**
 * 反柯里化：反柯里化，是一个泛型化的过程。它使得被反柯里化的函数，可以接收更多参数。使本来只有特定对象才适用的方法，扩展到更多的对象。
 * 
 * 即把如下给定的函数签名，
    obj.func(arg1, arg2)


    转化成一个函数形式，签名如下：
    func(obj, arg1, arg2)
 *  */ 


// 反柯里化 的一种
Function.prototype.uncarrying = function(){
    let that = this;
    return function(){
        // console.log( Function.prototype.call.apply(that,arguments) );
        console.log( arguments ); // that.push
        /*
        1. (Function.prototype.call).apply( that,arguments )
        2. (Function.prototype.call).apply( Array.prototype.push ,arguments)
        3. Array.prototype.push.call(...arguments)
        4. arguments[0].push( arguments.slice(2) )
        */
        return Function.prototype.call.apply(that,arguments);
        // return Array.prototype.push.call(arguments);
    }
}

// let myPush = Array.prototype.push.uncarrying(), obj = [];

// myPush(obj,1,2,3,4,6);

// console.log(myPush );

// console.log( obj );

// 原先的写法
var addEvent = function(el, type, fn, capture) {
    if (window.addEventListener) {
        el.addEventListener(type, function(e) {
            fn.call(el, e);
        }, capture);
    } else if (window.attachEvent) {
        el.attachEvent("on" + type, function(e) {
            fn.call(el, e);
        });
    } 
};

// 柯里化，只走一次判断
var addEvent = (function(){
    if (window.addEventListener) {
        return function(el, sType, fn, capture) {
            el.addEventListener(sType, function(e) {
                fn.call(el, e);
            }, (capture));
        };
    } else if (window.attachEvent) {
        return function(el, sType, fn, capture) {
            el.attachEvent("on" + sType, function(e) {
                fn.call(el, e);
            });
        };
    }
})();



// 正文： addEventListener、onclick 方式绑定函数如何追加自定义参数, 通过bind方法绑定额外参数
function bingDom(fn){
    var Slice = [].slice, args = Slice.call(arguments,2)
    return function eventCallBack(){
        // arguments.callee 属性指代当前正在执行的函数。
        var eventArgs = Slice.call(arguments)

        // 注意： 这里设置的是事件参数在前， 自定义参数在后
        return fn&&fn.apply(null, eventArgs.concat(args))
    }
}
// 示例： 事件监听添加额外参数
document.addEventListener('click',bingDom(function(event,test){
    console.log(test&&test.name, event);
},{ name:'六四' }))


// 自定义bind的实现
Function.prototype.myBind = function(){
    let args = Array.prototype.slice.call(arguments,0), thisArg = args[0], thisFunc = this;
    return function(){
        return thisFunc.call(thisArg, ...args.slice(1));
    }
}


// 自定义call的实现
Function.prototype.myCall = function(){
    const symbol = Symbol('key')
    let args = Array.prototype.slice.call(arguments,0), thisArg = args[0], thisFunc = this;
    thisArg[symbol] = thisFunc
    let result = thisArg[symbol](...args.slice(1))
    delete thisArg[symbol]

    return result
}

function testCall(){
    console.log(this.name,':', this.address, arguments);
}
const obj = {
    name: '王小明',
    address: '京海打到65165请问'
}

testCall.myCall( obj, 1,3,5)
